package rcs34.android.AndroidRC;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Date;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.text.format.Time;
import android.util.Base64;

import rcs34.android.AndroidRC.Contacts.AContacts;

public class HttpProcessor extends Thread {
  public final static String RESP_OK = "200 OK";
  private final static String ERR_NOT_FOUND = "404 Not Found";
  private final static String ERR_BAD_REQUEST = "400 Bad Request";
  private final static String ERR_INTERNAL_ERROR = "500 Internal Server Error";
  private final static String ERR_REQ_TIMEOUT = "408 Request Timeout";
  private final static String ERR_FORBIDDEN = "403 Forbidden";
  public final static String RESP_NEWLINE = "\r\n";
  private OutputStream os;
  private Socket socket;
  private HttpMessage httpMessage;
  private AndroidRCServer server;
  private Commandline commandline = new Commandline();
  private String password = "";
  private RC4 rc4 = null;
  
  public HttpProcessor(Socket _socket, AndroidRCServer _server) {
    try {
      server = _server;
      socket = _socket;
      os = socket.getOutputStream();
      SharedPreferences sharedPrefs = PreferenceManager
          .getDefaultSharedPreferences(GApp.context);
      password = sharedPrefs.getString(
          GApp.resources.getString(R.string.prefkeyPassword), "implicit");
      if (password == null || password.length() < 1) {
        password = "implicit";
      }
      rc4 = new RC4(password);
      httpMessage = new HttpMessage(socket);
    } catch (IOException e) {
      Utils.closeQuietly(socket);
      e.printStackTrace();
    }
  }
  
  public void forbidden() {
    sendError(ERR_FORBIDDEN,
        GApp.context.getString(R.string.httpipforbiddentitle),
        GApp.context.getString(R.string.httpipforbidden));
    try {
      socket.setSoTimeout(1000);
    } catch (SocketException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
  
  public void run() {
    server.fireChange(AndroidRCServer.MSG_CONNECTION_OPEN, "");
    try {
      while (!socket.isClosed()) {
        socket.setSoTimeout(5000);
        httpMessage.readMessage();
        processRequest();
        if (!httpMessage.keepAlive) {
          Utils.closeQuietly(socket);
        }
      }
    } catch (SocketException e) {
      e.printStackTrace();
    } catch (SocketTimeoutException e) {
      // e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      Utils.closeQuietly(socket);
      server.fireChange(AndroidRCServer.MSG_CONNECTION_CLOSE, "");
    }
  }
  
  public void processRequest() {
    if (!socket.isClosed()) {
      // Not ciphered.
      if (httpMessage.method.equals("GET")) {
        httpMessage.dropContent();
        CacheData cache = GApp.staticCache.get(httpMessage.path);
        if (cache != null) {
          send(httpMessage.composeResponse(cache.data, cache.mime,
              cache.cacheControl), cache.data);
          return;
        } else if (httpMessage.path.startsWith("/fm")) {
          String filename = FileManager.ROOT + File.separator
              + httpMessage.path.substring(4);
          File file = new File(filename);
          if (file.isFile() && file.canRead()) {
            this.send(file);
          }
        }
      } // POST - we do not expect any other method
      else {
        // Clear upload
        if (httpMessage.path.equals("/fm_clearupload")) {
          String filename = httpMessage.uri.getQueryParameter("file");
          long position = Long.parseLong(httpMessage.uri
              .getQueryParameter("position"));
          long adlerA = Long.parseLong(httpMessage.uri
              .getQueryParameter("adlerA"));
          long adlerB = Long.parseLong(httpMessage.uri
              .getQueryParameter("adlerB"));
          long adler32 = -1;
          if (adlerA >= 0 && adlerB >= 0) {
            adler32 = (adlerB << 16) | adlerA;
          }
          File output = new File(FileManager.ROOT + File.separator + filename);
          httpMessage.writeContentToFile(output, position, adler32);
          sendEncryptedObject(FileManager.lsAsJSON(output));
        } // Ciphered messages
        else if (httpMessage.path.equals("/rc4")) {
          try {
            JSONObject iData = readJSONData();
            if (iData == null) {
              byte[] data = "login".getBytes();
              send(httpMessage.composeResponse(data, HttpMessage.MIME_B64, ""),
                  data);
              return;
            }
            String area = iData.getString("area");
            // System.out.println(message);
            // Info
            if (area.equals("info")) {
              Utils.reportStorageInfo();
              sendEncryptedObject(GApp.infoJSON);
            } // LS
            else if (area.equals("fm")) {
              File file = new File(FileManager.ROOT + File.separator
                  + iData.getString("data"));
              if (file.exists() && file.isDirectory()) {
                sendEncryptedObject(FileManager.lsAsJSON(file));
              }
            } // New directory
            else if (area.equals("fm_mkdir")) {
              String filename = FileManager.ROOT + File.separator
                  + iData.getString("data");
              File file = new File(filename);
              if (file.mkdir()) {
                sendEncryptedObject(FileManager.lsAsJSON(file));
              } else {
                sendError(ERR_BAD_REQUEST,
                    GApp.context.getString(R.string.httpmkdirerrtitle),
                    GApp.context.getString(R.string.httpmkdirerr));
              }
            } // Delete file
            else if (area.equals("fm_delete")) {
              sendEncryptedObject(FileManager.delete(iData.getString("data")));
            }/*//File upload 
             else if (area.equals("fm_upload")) {
              JSONObject data=iData.getJSONObject("data");
              String filename = FileManager.ROOT + File.separator + data.getString("filename");
              long position = data.getLong("position");
              long adlerA = data.getLong("adlerA");
              long adlerB = data.getLong("adlerB");
              long adler32 = (adlerB << 16) | adlerA;
              byte[] fileData=data.get
              dataToFile(filename, position, adler32);
             }*/// Logcat
            else if (area.equals("logcat")) {
              sendEncryptedObject(Utils.newLogs());
            }// Vitalcheck
            else if (area.equals("vitalCheck")) {
              JSONObject response = new JSONObject();
              response.put("message", "hi");
              sendEncryptedObject(response);
            } // Contacts list
            else if (area.equals("contacts")) {
              sendEncryptedObject(AContacts.getContactsList());
            } // Contact details
            else if (area.equals("contactdetails")) {
              sendEncryptedObject(AContacts.getRawDetails(iData
                  .getString("data")));
            } // Create a new contact
            else if (area.equals("contactcreate")) {
              JSONObject result = AContacts.createRawContact(iData
                  .getJSONObject("data"));
              sendEncryptedObject(result);
            } // Delete a contact
            else if (area.equals("contactdelete")) {
              int count = AContacts.deleteRawContact(iData.getString("data"));
              JSONObject response = new JSONObject();
              response.put("count", count);
              sendEncryptedObject(response);
            } // Update a contact
            else if (area.equals("contactupdate")) {
              int count = AContacts.updateRawContact(iData
                  .getJSONObject("data"));
              JSONObject response = new JSONObject();
              response.put("count", count);
              sendEncryptedObject(response);
            } else if (area.equals("command")) {
              JSONArray cmd = iData.getJSONArray("data");
              String[] command = new String[cmd.length()];
              for (int i = 0; i < command.length; i++) {
                command[i] = cmd.getString(i);
              }
              sendEncryptedObject(commandline.processCommand(command));
            }
          } catch (JSONException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
          }
        } else {
          httpMessage.keepAlive = false;
          return;
        }
      }
    }
  }
  
  private JSONObject readJSONData() {
    byte[] data = httpMessage.getContent();
    if (data != null && data.length > 0) {
      try {
        rc4.reset();
        byte[] clear = rc4.proceed(data);
        String jsonString = new String(clear);
        JSONObject result = new JSONObject(jsonString);
        if (result.getString("password").equals(password)) {
          return result;
        }
      } catch (JSONException e) {
        // e.printStackTrace();
        return null;
      }
    }
    return null;
  }
  
  private void sendError(String _status, String _title, String _message) {
    String sContent = "<html><head>" + "<meta http-equiv=\"Content-Type\" "
        + "content=\"text/html; charset=utf-8\" />" + "</head>"
        + "<body style=\"background:#002200;color:#eeffee;\">" + "<h2>"
        + _title + "</h2>" + "<p>" + _message + "</p>" + "</body>" + "</html>";
    byte[] content = sContent.getBytes();
    String head = httpMessage.version + " " + _status
        + HttpProcessor.RESP_NEWLINE + "Content-Type: text/html"
        + HttpProcessor.RESP_NEWLINE + "Connection: close"
        + HttpProcessor.RESP_NEWLINE + "Content-Length: " + content.length
        + HttpProcessor.RESP_NEWLINE + HttpProcessor.RESP_NEWLINE;
    send(head.getBytes(), content);
  }
  
  private void send(byte[] _header, byte[] _data) {
    try {
      os.write(_header);
      if (_data != null) {
        os.write(_data);
      }
      os.flush();
    } catch (IOException e) {
      Utils.closeQuietly(socket);
      e.printStackTrace();
    }
  }
  
  private void sendEncryptedObject(JSONObject _json) {
    sendEncryptedJSON(_json.toString());
  }
  
  private void sendEncryptedArray(JSONArray _json) {
    sendEncryptedJSON(_json.toString());
  }
  
  private void sendEncryptedJSON(String _json) {
    rc4.reset();
    byte[] bytes = _json.getBytes();
    byte[] cipher = rc4.proceed(bytes);
    byte[] data = Base64.encode(cipher, Base64.DEFAULT);
    send(httpMessage.composeResponse(data, HttpMessage.MIME_B64, ""), data);
  }
  
  private void send(File _file) {
    try {
      os.write(httpMessage.composeResponse(_file));
      FileInputStream is = new FileInputStream(_file);
      byte[] buffer = new byte[1024 * 1024];
      while (is.read(buffer) >= 0) {
        os.write(buffer);
      }
    } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
}
